<!doctype html>

<html>

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">

  <script src="../../webcomponentsjs/webcomponents-lite.js"></script>
  <script src="../../web-component-tester/browser.js"></script>
  <script src="../../iron-test-helpers/mock-interactions.js"></script>
  <link rel="import" href="../../test-fixture/test-fixture.html">

  <link rel="import" href="helpers.html">
  <link rel="import" href="../vaadin-grid.html">
  <link rel="import" href="../vaadin-grid-column-group.html">
</head>

<body>
  <test-fixture id="default">
    <template>
      <vaadin-grid theme="no-border">
        <template class="row-details">
          <input type="text">
        </template>
        <vaadin-grid-column>
          <template class="header"></template>
          <template>[[index]] [[item]]</template>
          <template class="footer"></template>
        </vaadin-grid-column>
        <vaadin-grid-column>
          <template class="header">
            <input>
          </template>
          <template>
            <input>
          </template>
          <template class="footer">
            <input>
          </template>
        </vaadin-grid-column>
        <vaadin-grid-column>
          <template class="header">
            <div></div>
            <div></div>
          </template>
          <template>
            <span>
              [[index]] [[item]]
            </span>
          </template>
          <template class="footer"></template>
        </vaadin-grid-column>
      </vaadin-grid>
      <input id="focusable">
    </template>
  </test-fixture>

  <test-fixture id="groups">
    <template>
      <vaadin-grid>
        <vaadin-grid-column-group>
          <template class="header">main group header</template>
          <template class="footer">main group footer</template>

          <vaadin-grid-column-group>
            <template class="header">sub group header</template>

            <vaadin-grid-column>
              <template class="header">column header</template>
              <template class="footer">column footer</template>
              <template>[[index]] [[item]]</template>
            </vaadin-grid-column>
          </vaadin-grid-column-group>
        </vaadin-grid-column-group>
      </vaadin-grid>
    </template>
  </test-fixture>

  <script>
    window.top.focus && window.top.focus();
    window.focus();

    if ((window.chrome
      || /HeadlessChrome/.test(window.navigator.userAgent)
      || window.navigator.userAgent.toLowerCase().indexOf('firefox') > -1)
      && !window.document.hasFocus()
      && !window.top.document.hasFocus()) {
      // Oh no! Focus kinda works, but no native events are dispatched. Let’s fake them.
      const nativeFocus = HTMLElement.prototype.focus;
      let fakeFocusCurrentTarget, recursiveFocusInterrupt;
      HTMLElement.prototype.focus = function() {
        const dispatchMockFocusEvent = function(type, bubbles, composed, target, relatedTarget) {
          if (!target) {
            return;
          }
          const e = new CustomEvent(type, {bubbles, composed: composed});
          e.relatedTarget = relatedTarget;
          target.dispatchEvent(e);
        };

        let activeElement, recursiveFocusInvoke = false;
        if (fakeFocusCurrentTarget) {
          // When focus is called from a focusin/focusout listener (e. g.,
          // recursive .focus()), activeElement might not be shifted yet.
          // The true activeElement is a target of the previous .focus call.
          activeElement = fakeFocusCurrentTarget;
          recursiveFocusInvoke = true;
        } else {
          activeElement = document.activeElement;
          if (activeElement) {
            while (activeElement.shadowRoot && activeElement.shadowRoot.activeElement) {
              activeElement = activeElement.shadowRoot.activeElement;
            }
          }
        }

        if (activeElement === this) {
          // Prevent duplicate events when focus stays on the same element
          nativeFocus.apply(this, arguments);
        } else {
          fakeFocusCurrentTarget = this;
          // If shadow roots match, the events are shadow-internal, not composed
          const composed = this.getRootNode() !== activeElement.getRootNode();
          dispatchMockFocusEvent('focusout', true, composed, activeElement, this);
          !recursiveFocusInterrupt && dispatchMockFocusEvent('focusin', true, composed, this, activeElement);
          !recursiveFocusInterrupt && nativeFocus.apply(this, arguments);
          !recursiveFocusInterrupt && dispatchMockFocusEvent('blur', false, composed, activeElement, this);
          !recursiveFocusInterrupt && dispatchMockFocusEvent('focus', false, composed, this, activeElement);
          fakeFocusCurrentTarget = undefined;
        }

        // Recursive focus invoke should interrupt the parent
        recursiveFocusInterrupt = recursiveFocusInvoke;
      };
    }

    let grid, focusable, scroller, header, footer, body;

    function clickItem(rowIndex) {
      return getCellContent(getRowFirstCell(rowIndex)).click();
    }

    function focusItem(rowIndex) {
      return getRowFirstCell(rowIndex).focus();
    }

    function getRowCell(rowIndex, cellIndex) {
      return grid.$.items.children[rowIndex].children[cellIndex];
    }

    function getRowFirstCell(rowIndex) {
      return getRowCell(rowIndex, 0);
    }

    function focusFirstBodyInput(rowIndex) {
      const cell = getRowCell(rowIndex || 0, 1);

      const input = getCellContent(cell).children[0];
      input.focus();
      return input;
    }

    function tab(target) {
      MockInteractions.keyDownOn(target || grid.shadowRoot.activeElement, 9, [], 'Tab');
    }

    function shiftTab(target) {
      MockInteractions.keyDownOn(target || grid.shadowRoot.activeElement, 9, 'shift', 'Tab');
    }

    function left(target) {
      MockInteractions.keyDownOn(target || grid.shadowRoot.activeElement, 37, [], 'ArrowLeft');
    }

    function up(target) {
      MockInteractions.keyDownOn(target || grid.shadowRoot.activeElement, 38, [], 'ArrowUp');
    }

    function right(target) {
      MockInteractions.keyDownOn(target || grid.shadowRoot.activeElement, 39, [], 'ArrowRight');
    }

    function down(target) {
      MockInteractions.keyDownOn(target || grid.shadowRoot.activeElement, 40, [], 'ArrowDown');
    }

    function spaceDown(target) {
      MockInteractions.keyDownOn(target || grid.shadowRoot.activeElement, 32, [], ' ');
    }

    function spaceUp(target) {
      MockInteractions.keyUpOn(target || grid.shadowRoot.activeElement, 32, [], ' ');
    }

    function pageUp(target) {
      MockInteractions.keyDownOn(target || grid.shadowRoot.activeElement, 33, [], 'PageUp');
    }

    function pageDown(target) {
      MockInteractions.keyDownOn(target || grid.shadowRoot.activeElement, 34, [], 'PageDown');
    }

    function end(target) {
      MockInteractions.keyDownOn(target || grid.shadowRoot.activeElement, 35, [], 'End');
    }

    function ctrlEnd(target) {
      MockInteractions.keyDownOn(target || grid.shadowRoot.activeElement, 35, 'ctrl', 'End');
    }

    function home(target) {
      MockInteractions.keyDownOn(target || grid.shadowRoot.activeElement, 36, [], 'Home');
    }

    function ctrlHome(target) {
      MockInteractions.keyDownOn(target || grid.shadowRoot.activeElement, 36, 'ctrl', 'Home');
    }

    function enter(target) {
      MockInteractions.keyDownOn(target || grid.shadowRoot.activeElement, 13, [], 'Enter');
    }

    function escape(target) {
      MockInteractions.keyDownOn(target || grid.shadowRoot.activeElement, 27, [], 'Escape');
    }

    function f2(target) {
      MockInteractions.keyDownOn(target || grid.shadowRoot.activeElement, 113, [], 'F2');
    }

    function getFirstHeaderCell() {
      return grid.$.header.children[0].children[0];
    }

    function focusWithMouse(element, mouseTarget) {
      mouseTarget = mouseTarget || element;
      MockInteractions.down(mouseTarget);
      element.focus();
      MockInteractions.up(mouseTarget);
    }

    function focusHeaderInput() {
      const cell = grid.$.header.children[0].children[1];
      const contents = getCellContent(cell);
      contents.children[0].focus();
    }

    function focusFooterInput() {
      const cell = grid.$.footer.children[0].children[1];
      const contents = getCellContent(cell);
      contents.children[0].focus();
    }

    function focusFirstHeaderCell() {
      focusWithMouse(getFirstHeaderCell());
    }

    function focusFirstFooterCell() {
      focusWithMouse(grid.$.footer.children[0].children[0]);
    }

    function tabToHeader() {
      grid._headerFocusable.focus();
    }

    function tabToBody() {
      grid._itemsFocusable.focus();
    }

    function shiftTabToFooter() {
      grid._footerFocusable.focus();
    }

    function getFocusedCellIndex() {
      const focusedCell = grid.shadowRoot.activeElement;
      return Array.from(focusedCell.parentNode.children).indexOf(focusedCell);
    }

    function getFocusedRowIndex() {
      const focusedRow = grid.shadowRoot.activeElement.parentNode;
      return Array.from(focusedRow.parentNode.children).indexOf(focusedRow);
    }

    function getFocusableCellIndex() {
      return Array.from(grid._itemsFocusable.parentNode.children).indexOf(grid._itemsFocusable);
    }

    function getFocusableRowIndex() {
      return grid._itemsFocusable.parentNode.index;
    }

    function getTabbableElements(root) {
      return root.querySelectorAll('[tabindex]:not([tabindex="-1"])');
    }

    const ios = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
    const android = /android/i.test(navigator.userAgent);

    !ios && !android && describe('keyboard navigation', () => {
      before(done => {
        const fixtureElements = fixture('default');
        grid = fixtureElements[0];
        focusable = fixtureElements[1];

        scroller = grid.$.scroller;
        header = grid.$.header;
        body = grid.$.items;
        footer = grid.$.footer;

        grid._observer.flush();

        flushGrid(grid);

        flush(done);
      });

      beforeEach(() => {
        if (!grid.parentElement) {
          document.body.appendChild(grid);
          document.body.appendChild(focusable);
        }

        // Reset side effects from tests
        grid.style.width = '';
        grid.style.border = '';

        if (grid.items[0] !== 'foo' || grid.items[1] !== 'bar') {
          grid.size = undefined;
          grid.dataProvider = undefined;
          grid.items = ['foo', 'bar'];
          flushGrid(grid);
        }
        grid.activeItem = null;
        grid.detailsOpenedItems = [];
        grid._columnTree[0].forEach(column => column.hidden = column.frozen = false);

        grid._focusedItemIndex = 0;
        grid._focusedColumnOrder = undefined;
        grid._resetKeyboardNavigation();
        grid.removeAttribute('navigating');
        focusable.focus();
      });

      describe('navigation mode', () => {
        it('should not be in navigation mode by default', () => {
          expect(grid.hasAttribute('navigating')).to.be.false;
        });

        it('should not enable navigation mode when cell is clicked', () => {
          focusFirstHeaderCell();

          expect(grid.hasAttribute('navigating')).to.be.false;
        });

        it('should disable navigation mode when blurred', () => {
          focusFirstHeaderCell();
          grid.setAttribute('navigating', '');

          focusable.focus();

          expect(grid.hasAttribute('navigating')).to.be.false;
        });

        it('should enable navigation mode when tabbed into header', () => {
          // simulating tabbing into header
          tabToHeader();

          expect(grid.hasAttribute('navigating')).to.be.true;
        });

        it('should enable navigation mode when tabbed inside header', () => {
          focusFirstHeaderCell();

          tab();

          expect(grid.hasAttribute('navigating')).to.be.true;
        });

        it('should enable navigation mode when tabbed into footer', () => {
          // simulating tabbing into footer
          grid.nextElementSibling.focus();
          shiftTabToFooter();

          expect(grid.hasAttribute('navigating')).to.be.true;
        });

        it('should enable navigation mode when tabbed inside footer', () => {
          focusFirstFooterCell();

          tab();

          expect(grid.hasAttribute('navigating')).to.be.true;
        });

        it('should enable navigation mode with page down', () => {
          focusItem(0);

          pageDown();

          expect(grid.hasAttribute('navigating')).to.be.true;
        });

        it('should enable navigation mode with page up', () => {
          focusItem(0);

          pageUp();

          expect(grid.hasAttribute('navigating')).to.be.true;
        });

        it('should enable navigation mode with home', () => {
          focusItem(0);

          home();

          expect(grid.hasAttribute('navigating')).to.be.true;
        });

        it('should enable navigation mode with end', () => {
          focusItem(0);

          end();

          expect(grid.hasAttribute('navigating')).to.be.true;
        });

        it('should not enable navigation mode on mouse cell focus', () => {
          focusWithMouse(getRowFirstCell(0));

          expect(grid.hasAttribute('navigating')).to.be.false;
        });

        it('should not enable navigation mode on mouse cell content focus', () => {
          const cell = getRowFirstCell(0);
          focusWithMouse(cell, getCellContent(cell));

          expect(grid.hasAttribute('navigating')).to.be.false;
        });

        it('should disable navigation mode on mousedown', () => {
          focusFirstHeaderCell();
          grid.setAttribute('navigating', '');

          MockInteractions.down(grid);

          expect(grid.hasAttribute('navigating')).to.be.false;
        });
      });

      describe('navigating with tab', () => {
        beforeEach(() => {
          grid.setAttribute('navigating', '');

          grid._observer.flush();
        });

        it('should have single tabbable cell in every section', () => {
          const tabbableElements = getTabbableElements(grid.shadowRoot);
          expect(tabbableElements.length).to.equal(5);
          expect(tabbableElements[0]).to.equal(grid.$.table);
          expect(tabbableElements[1].parentNode.parentNode).to.equal(grid.$.header);
          expect(tabbableElements[2].parentNode.parentNode).to.equal(grid.$.items);
          expect(tabbableElements[3].parentNode.parentNode).to.equal(grid.$.footer);
          expect(tabbableElements[4]).to.equal(grid.$.focusexit);
        });

        it('should have tabindex 0 on tabbable cells', () => {
          const tabbableElements = getTabbableElements(grid.shadowRoot);
          const tabIndexes = Array.from(tabbableElements).map(el => el.tabIndex);
          expect(tabIndexes).to.eql([0, 0, 0, 0, 0]);
        });

        it('should have a focus exit as the very last child', () => {
          expect(grid.$.focusexit).to.be.ok;
          expect(grid.$.focusexit.tabIndex).to.equal(0);
          const lastChild = Array.from(grid.shadowRoot.children)
            .filter(child => child.localName !== 'style')
            .pop();
          expect(lastChild).to.equal(grid.$.focusexit);
        });

        it('should be possible to tab through the grid', () => {
          const tabbableElements = getTabbableElements(grid.shadowRoot);

          let keydownEvent;
          const listener = (e) => keydownEvent = e;

          // assuming grid has been tabbed into.
          tabbableElements[1].focus();

          listenOnce(grid.$.scroller, 'keydown', listener);
          tab(); // to body cell
          expect(keydownEvent.defaultPrevented).to.be.true;
          expect(grid.shadowRoot.activeElement).to.equal(tabbableElements[2]);

          listenOnce(grid.$.scroller, 'keydown', listener);
          tab(); // to footer cell
          expect(keydownEvent.defaultPrevented).to.be.true;
          expect(grid.shadowRoot.activeElement).to.equal(tabbableElements[3]);
        });

        it('should be possible to shift-tab through grid', () => {
          const tabbableElements = getTabbableElements(grid.shadowRoot);

          let keydownEvent;
          const listener = (e) => keydownEvent = e;

          // assuming grid has been shift-tabbed into.
          tabbableElements[3].focus();

          listenOnce(grid.$.scroller, 'keydown', listener);
          shiftTab(); // to body cell
          expect(keydownEvent.defaultPrevented).to.be.true;
          expect(grid.shadowRoot.activeElement).to.equal(tabbableElements[2]);

          listenOnce(grid.$.scroller, 'keydown', listener);
          shiftTab(); // to header cell
          expect(keydownEvent.defaultPrevented).to.be.true;
          expect(grid.shadowRoot.activeElement).to.equal(tabbableElements[1]);
        });

        it('should be possible to tab through the grid when first column is hidden', () => {
          const firstColumn = grid.querySelectorAll('vaadin-grid-column')[0];
          firstColumn.hidden = true;

          const tabbableElements = getTabbableElements(grid.shadowRoot);

          let keydownEvent;
          const listener = (e) => keydownEvent = e;

          // assuming grid has been tabbed into.
          tabbableElements[1].focus();

          listenOnce(grid.$.scroller, 'keydown', listener);
          tab(); // to body cell
          expect(keydownEvent.defaultPrevented).to.be.true;
          expect(grid.shadowRoot.activeElement).to.equal(tabbableElements[2]);

          listenOnce(grid.$.scroller, 'keydown', listener);
          tab(); // to footer cell
          expect(keydownEvent.defaultPrevented).to.be.true;
          expect(grid.shadowRoot.activeElement).to.equal(tabbableElements[3]);
        });

        it('should be possible to shift-tab through grid when first column is hidden', () => {
          const firstColumn = grid.querySelectorAll('vaadin-grid-column')[0];
          firstColumn.hidden = true;

          const tabbableElements = getTabbableElements(grid.shadowRoot);

          let keydownEvent;
          const listener = (e) => keydownEvent = e;

          // assuming grid has been shift-tabbed into.
          tabbableElements[3].focus();

          listenOnce(grid.$.scroller, 'keydown', listener);
          shiftTab(); // to body cell
          expect(keydownEvent.defaultPrevented).to.be.true;
          expect(grid.shadowRoot.activeElement).to.equal(tabbableElements[2]);

          listenOnce(grid.$.scroller, 'keydown', listener);
          shiftTab(); // to header cell
          expect(keydownEvent.defaultPrevented).to.be.true;
          expect(grid.shadowRoot.activeElement).to.equal(tabbableElements[1]);
        });

        it('should be possible to exit grid with tab', () => {
          const tabbableElements = getTabbableElements(grid.shadowRoot);
          tabbableElements[3].focus(); // focus footer cell

          let keydownEvent;
          listenOnce(grid.shadowRoot.activeElement, 'keydown', (e) => keydownEvent = e);
          tab();

          // Expect programmatic focus on focus exit element
          expect(grid.shadowRoot.activeElement).to.equal(grid.$.focusexit);
          // Ensure native focus jump is allowed
          expect(keydownEvent.defaultPrevented).to.be.false;
        });

        it('should be possible to exit grid with shift+tab', () => {
          const tabbableElements = getTabbableElements(grid.shadowRoot);
          tabbableElements[1].focus(); // focus header cell

          let keydownEvent;
          listenOnce(grid.shadowRoot.activeElement, 'keydown', (e) => keydownEvent = e);
          shiftTab();

          // Expect programmatic focus on focus exit element
          expect(grid.shadowRoot.activeElement).to.equal(grid.$.table);
          // Ensure native focus jump is allowed
          expect(keydownEvent.defaultPrevented).to.be.false;
        });

        it('should be possible to enter grid with tab', () => {
          const tabbableElements = getTabbableElements(grid.shadowRoot);

          // focusin on table element — same as tab from above the grid
          const event = new CustomEvent('focusin', {bubbles: true, composed: true});
          event.relatedTarget = focusable;
          tabbableElements[0].dispatchEvent(event);

          // Expect programmatic focus on header cell
          expect(grid.shadowRoot.activeElement).to.equal(tabbableElements[1]);
        });

        it('should be possible to enter grid with shift+tab', () => {
          const tabbableElements = getTabbableElements(grid.shadowRoot);

          // focusin on focusexit element — same as shift+tab from below the grid
          const event = new CustomEvent('focusin', {bubbles: true, composed: true});
          event.relatedTarget = focusable;
          tabbableElements[4].dispatchEvent(event);

          // Expect programmatic focus on footer cell
          expect(grid.shadowRoot.activeElement).to.equal(tabbableElements[3]);
        });

        it('should set native focus to header on header cell click', () => {
          const tabbableElements = getTabbableElements(grid.shadowRoot);
          focusFirstHeaderCell();

          expect(grid.shadowRoot.activeElement).to.equal(tabbableElements[1]);
        });

        it('should set native focus to body on body cell click', () => {
          const tabbableElements = getTabbableElements(grid.shadowRoot);
          focusItem(0);
          clickItem(0);

          expect(grid.shadowRoot.activeElement).to.equal(tabbableElements[2]);
        });

        it('should set native focus to footer on footer cell click', () => {
          const tabbableElements = getTabbableElements(grid.shadowRoot);
          focusFirstFooterCell();

          expect(grid.shadowRoot.activeElement).to.equal(tabbableElements[3]);
        });

        it('should move header tabbable cell when navigating with keys', () => {
          tabToHeader();

          right();

          expect(getTabbableElements(header)[0]).to.equal(header.children[0].children[1]);
        });

        it('should move body tabbable cell when navigating with keys', () => {
          tabToBody();

          right();

          expect(getTabbableElements(body)[0]).to.equal(body.children[0].children[1]);
        });

        it('should move footer tabbable cell when navigating with keys', () => {
          shiftTabToFooter();

          right();

          expect(getTabbableElements(footer)[0]).to.equal(footer.children[0].children[1]);
        });
      });

      describe('navigating with keys', () => {
        it('should enable navigation mode on down', () => {
          focusItem(0);

          down();

          expect(grid.hasAttribute('navigating')).to.be.true;
        });

        it('should navigate on down when navigation mode is off', () => {
          focusItem(0);

          down();

          expect(getFocusedRowIndex()).to.equal(1);
        });

        it('should enable navigation mode on up', () => {
          focusItem(0);

          up();

          expect(grid.hasAttribute('navigating')).to.be.true;
        });

        it('should navigate on up when navigation mode is off', () => {
          focusItem(1);

          up();

          expect(getFocusedRowIndex()).to.equal(0);
        });

        it('should enable navigation mode on left', () => {
          focusItem(0);

          left();

          expect(grid.hasAttribute('navigating')).to.be.true;
        });

        it('should enable navigation mode on right', () => {
          focusItem(0);

          right();

          expect(grid.hasAttribute('navigating')).to.be.true;
        });

        ['rtl', 'ltr'].forEach(direction => {
          describe(`navigation left / right in ${direction}`, () => {
            beforeEach(() => grid.setAttribute('dir', direction));

            it('should navigate on left when navigation mode is off', () => {
              getRows(body)[0].children[1].focus();

              left();

              expect(getFocusedCellIndex()).to.equal(direction === 'rtl' ? 2 : 0);
            });

            it('should navigate on right when navigation mode is off', () => {
              getRows(body)[0].children[1].focus();

              right();

              expect(getFocusedCellIndex()).to.equal(direction === 'rtl' ? 0 : 2);
            });

          });
        });

        it('should focus cell below with down', () => {
          focusItem(0);

          down();

          expect(getFocusedRowIndex()).to.equal(1);
          expect(getFocusedCellIndex()).to.equal(0);
        });

        it('should focus cell above with up', () => {
          focusItem(0);
          down();

          up();

          expect(getFocusedRowIndex()).to.equal(0);
          expect(getFocusedCellIndex()).to.equal(0);
        });

        it('should focus cell left with left', () => {
          focusItem(0);
          right();

          left();

          expect(getFocusedCellIndex()).to.equal(0);
          expect(getFocusedRowIndex()).to.equal(0);
        });

        it('should focus cell right with right', () => {
          focusItem(0);

          right();

          expect(getFocusedCellIndex()).to.equal(1);
          expect(getFocusedRowIndex()).to.equal(0);
        });

        describe('column _order support', () => {
          beforeEach(() => {
            grid._columnTree[0][0]._order = 100000000;
            grid._columnTree[0][1]._order = 200000000;
            grid._columnTree[0][2]._order = 1100000000;
          });

          it('should follow _order when navigating right', () => {
            focusItem(0);

            right();

            expect(getFocusedCellIndex()).to.equal(1);
          });

          it('should follow _order when navigating left', () => {
            getRowCell(0, 2).focus();

            left();

            expect(getFocusedCellIndex()).to.equal(1);
          });
        });

        describe('with hidden columns', () => {
          it('should skip over hidden column with right arrow', () => {
            grid._columnTree[0][1].hidden = true;
            flushGrid(grid);

            body.children[0].children[0].focus();

            right();

            expect(getFocusedCellIndex()).to.equal(1);
            const columnFocusedCell = grid.shadowRoot.activeElement._column;
            expect(columnFocusedCell.hidden).to.be.false;
          });

          it('should skip over hidden column with left arrow', () => {
            grid._columnTree[0][1].hidden = true;
            body.children[0].children[0].focus();
            right();

            left();

            expect(getFocusedCellIndex()).to.equal(0);
          });

          it('should not navigate to hidden column with left arrow', () => {
            grid._columnTree[0][0].hidden = true;
            flushGrid(grid);

            body.children[0].children[1].focus();

            left();

            expect(getFocusedCellIndex()).to.equal(0);
            const columnFocusedCell = grid.shadowRoot.activeElement._column;
            expect(columnFocusedCell.hidden).to.be.false;
          });

          it('should not navigate to hidden column with right arrow', () => {
            grid._columnTree[0][2].hidden = true;
            body.children[0].children[0].focus();

            right();
            right();

            expect(getFocusedCellIndex()).to.equal(1);
          });

          it('should not navigate to hidden column with end', () => {
            grid._columnTree[0][0].hidden = true;
            flushGrid(grid);

            body.children[0].children[1].focus();

            home();

            expect(getFocusedCellIndex()).to.equal(0);
            const columnFocusedCell = grid.shadowRoot.activeElement._column;
            expect(columnFocusedCell.hidden).to.be.false;
          });

          it('should not navigate to hidden column with home', () => {
            grid._columnTree[0][2].hidden = true;
            flushGrid(grid);

            body.children[0].children[0].focus();

            end();

            expect(getFocusedCellIndex()).to.equal(1);
            const columnFocusedCell = grid.shadowRoot.activeElement._column;
            expect(columnFocusedCell.hidden).to.be.false;

          });
        });

        describe('with row details', () => {
          beforeEach(() => {
            flushGrid(grid);
            grid.openItemDetails(grid.items[0]);

            tabToBody();
          });

          function findRowDetailsCell(scope) {
            return scope.querySelector('[part~="details-cell"]');
          }

          it('should not navigate to row details with right arrow', () => {
            right(); // index 1
            right(); // index 2

            right();
            expect(findRowDetailsCell(grid.shadowRoot.activeElement.parentNode)).to.not.equal(grid.shadowRoot.activeElement);
            expect(getFocusedCellIndex()).to.equal(2);

          });

          it('should not navigate to row details with end', () => {
            end();

            expect(getFocusedCellIndex()).to.equal(2);
            expect(findRowDetailsCell(grid.shadowRoot.activeElement.parentNode)).to.not.equal(grid.shadowRoot.activeElement);
          });

          it('should navigate to row details with down arrow', () => {
            down();

            expect(findRowDetailsCell(grid.shadowRoot.activeElement.parentNode)).to.equal(grid.shadowRoot.activeElement);
            expect(getFocusedRowIndex()).to.equal(0);
          });

          it('should navigate from row details with down arrow', () => {
            down();

            down();

            expect(findRowDetailsCell(grid.shadowRoot.activeElement.parentNode)).to.not.equal(grid.shadowRoot.activeElement);
            expect(getFocusedRowIndex()).to.equal(1);
            expect(getFocusedCellIndex()).to.equal(0);
          });

          it('should preserve the focused cell index while navigating through details', () => {
            right();
            down();

            down();

            expect(getFocusedRowIndex()).to.equal(1);
            expect(getFocusedCellIndex()).to.equal(1);
          });

          it('should not navigate right while in details', () => {
            down();

            right();
            down();

            expect(getFocusedRowIndex()).to.equal(1);
            expect(getFocusedCellIndex()).to.equal(0);
          });

          it('should not navigate to end while in details', () => {
            down();

            end();
            down();

            expect(getFocusedRowIndex()).to.equal(1);
            expect(getFocusedCellIndex()).to.equal(0);
          });

          it('should not navigate left while in details', () => {
            right();
            down();

            left();
            down();

            expect(getFocusedRowIndex()).to.equal(1);
            expect(getFocusedCellIndex()).to.equal(1);
          });

          it('should not navigate to home while in details', () => {
            right();
            down();

            home();
            down();

            expect(getFocusedRowIndex()).to.equal(1);
            expect(getFocusedCellIndex()).to.equal(1);
          });

          it('should navigate to row details with arrow up', () => {
            down();
            down();

            up();

            expect(findRowDetailsCell(grid.shadowRoot.activeElement.parentNode)).to.equal(grid.shadowRoot.activeElement);
            expect(getFocusedRowIndex()).to.equal(0);
            expect(getFocusedCellIndex()).to.not.equal(0);
          });

          it('should navigate from row details with arrow up', () => {
            down();
            down();
            up();

            up();

            expect(findRowDetailsCell(grid.shadowRoot.activeElement.parentNode)).to.not.equal(grid.shadowRoot.activeElement);
            expect(getFocusedRowIndex()).to.equal(0);
            expect(getFocusedCellIndex()).to.equal(0);
          });

          it('should set focused cell when tapping on details cell', () => {
            const rowDetailsCell = findRowDetailsCell(body.children[0]);
            focusWithMouse(rowDetailsCell, rowDetailsCell._content);

            expect(findRowDetailsCell(grid.shadowRoot.activeElement.parentNode)).to.equal(grid.shadowRoot.activeElement);
          });

          it('should focus on the first element inside details with enter', done => {
            down();

            animationFrameFlush(() => {
              const input = getCellContent(grid.shadowRoot.activeElement).children[0];
              const spy = sinon.spy(input, 'focus');

              enter();

              expect(spy.callCount).to.equal(1);
              spy.restore();
              done();
            });
          });
        });

        it('should focus first cell with home', () => {
          focusItem(0);
          right();

          home();

          expect(getFocusedCellIndex()).to.equal(0);
        });

        it('should focus first cell on first row with ctrl+home', () => {
          focusItem(0);
          right();

          ctrlHome();

          expect(getFocusedCellIndex()).to.equal(0);
          expect(getFocusedRowIndex()).to.equal(0);
        });

        it('should focus last cell with end', () => {
          focusItem(0);

          end();

          expect(getFocusedCellIndex()).to.equal(2);
        });


        it('should focus last cell on last row with ctrl+end', () => {
          focusItem(0);

          ctrlEnd();

          expect(getFocusedCellIndex()).to.equal(2);
          expect(getFocusedRowIndex()).to.equal(1);
        });

        it('should focus to last row element after scrolling to end', () => {
          grid.items = undefined;
          grid.size = 200;
          grid.dataProvider = infiniteDataProvider;
          flushGrid(grid);

          focusItem(0);

          ctrlEnd();

          expect(grid.shadowRoot.activeElement.parentNode.index).to.equal(grid.items.length - 1);
        });

        describe('horizontal scrolling', () => {
          beforeEach(() => {
            grid.style.width = '100px'; // column default min width is 100px
          });

          it('should scroll cells visible with right arrow on header', () => {
            focusFirstHeaderCell();
            down();

            right();

            expect(grid.$.table.scrollLeft).to.be.at.least(100);
          });

          it('should scroll cells visible with right arrow on body', () => {
            focusItem(0);
            down();

            right();

            expect(grid.$.table.scrollLeft).to.be.at.least(100);
          });

          it('should scroll cells visible with right arrow on footer', () => {
            focusFirstFooterCell();
            down();

            right();

            expect(grid.$.table.scrollLeft).to.be.at.least(100);
          });

          it('should scroll cells visible with left arrow on header', () => {
            focusFirstHeaderCell();
            down();
            right();

            left();

            expect(grid.$.table.scrollLeft).to.equal(0);
          });

          it('should scroll cells visible with left arrow on body', () => {
            focusItem(0);
            down();
            right();

            left();

            expect(grid.$.table.scrollLeft).to.equal(0);
          });

          it('should scroll cells visible with home', () => {
            focusItem(0);
            grid.$.table.scrollLeft = 999999999;

            home();

            expect(grid.$.table.scrollLeft).to.equal(0);
          });

          it('should scroll cells visible with end', () => {
            focusItem(0);

            end();

            expect(grid.$.table.scrollLeft).to.equal(grid.$.table.scrollWidth - grid.$.table.clientWidth);
          });

          it('should scroll cell visible under from frozen cells with left arrow', done => {
            grid.style.width = '200px'; // column default min width is 100px
            grid.style.border = 'none';
            grid._columnTree[0][0].frozen = true;

            focusItem(0);
            right();
            right();

            grid.async(() => {
              left();
              expect(grid.$.table.scrollLeft).to.equal(0);
              done();
            });
          });

          it('should scroll cells visible with left arrow on footer', () => {
            focusFirstFooterCell();
            down();
            right();

            left();

            expect(grid.$.table.scrollLeft).to.equal(0);
          });
        });

        describe('vertical scrolling', () => {
          beforeEach(done => {
            grid.items = undefined;
            grid.size = 200;
            grid.dataProvider = infiniteDataProvider;
            grid._scrollToIndex(0);
            flushGrid(grid);
            animationFrameFlush(done);
          });

          it('should scroll rows visible with up arrow', () => {
            focusItem(0);
            grid._scrollToIndex(100);

            up();

            expect(grid.$.table.scrollTop).to.equal(0);
          });

          it('should scroll rows visible with down arrow', () => {
            focusItem(grid._lastVisibleIndex);

            down();

            expect(grid.$.table.scrollTop).to.be.above(0);
          });

          it('should scroll to the first row with ctrl+home', () => {
            focusItem(0);

            scrollToEnd(grid);

            ctrlHome();
            expect(grid.$.table.scrollTop).to.equal(0);
          });

          it('should scroll to the last row with ctrl+end', () => {
            focusItem(0);

            ctrlEnd();

            expect(grid.$.table.scrollTop).to.equal(grid.$.table.scrollHeight - grid.$.table.clientHeight);
          });

          it('should scroll down one page with page down', () => {
            focusItem(0);
            const previousLastVisibleIndex = grid._lastVisibleIndex;

            pageDown();

            expect(grid._lastVisibleIndex).to.be.gt(1); // sanity check
            expect(getFocusedRowIndex()).to.equal(previousLastVisibleIndex - 1);
          });

          it('should scroll up one page with page up', done => {
            focusItem(0);
            pageDown();

            grid.async(() => {
              pageUp();

              expect(getFocusedRowIndex()).to.equal(0);
              done();
            });
          });

          it('should scroll the focused item visible when focus is set to body', done => {
            scrollToEnd(grid);

            animationFrameFlush(() => {
              tabToHeader();
              tab();
              tabToBody();

              expect(grid.$.table.scrollTop).to.equal(0);

              done();
            });
          });

          it('should have the right cell focused after size change', () => {
            scrollToEnd(grid);
            getCell(grid, 0).focus();
            up();

            let focusedRow = grid.$.items.children[getFocusedRowIndex()];
            const focusedContent = getCellContent(getRowCells(focusedRow)[0]).textContent;

            grid.size *= 2;

            focusedRow = grid.$.items.children[getFocusedRowIndex()];
            expect(getCellContent(getRowCells(focusedRow)[0]).textContent).to.equal(focusedContent);
          });

          it('should not focus a cell on size change if the focus is outside the body', () => {
            getContainerCell(grid.$.header, 0, 0).focus();
            up();

            grid.size *= 2;
            expect(grid.shadowRoot.activeElement).to.equal(getContainerCell(grid.$.header, 0, 0));
          });

          it('should not scroll if the new focused item in viewport', () => {
            focusItem(0);
            ctrlEnd();
            const scrollTop = grid.$.table.scrollTop;
            up();
            expect(grid.$.table.scrollTop).to.equal(scrollTop);
          });

          describe('rotating focus indicator prevention', () => {
            it('should hide navigation mode when a focused row goes off screen', () => {
              focusItem(0);
              right();

              expect(grid.hasAttribute('navigating')).to.be.true;

              grid._scrollToIndex(100);

              expect(grid.hasAttribute('navigating')).to.be.false;
            });

            it('should reveal navigation mode when a focused row is back on screen', () => {
              focusItem(0);
              right();
              grid._scrollToIndex(100);

              grid._scrollToIndex(0);

              expect(grid.hasAttribute('navigating')).to.be.true;
            });

            it('should not hide navigation mode if a header cell is focused', () => {
              focusFirstHeaderCell();
              right();

              expect(grid.hasAttribute('navigating')).to.be.true;

              grid._scrollToIndex(100);

              expect(grid.hasAttribute('navigating')).to.be.true;
            });
          });
        });
      });

      describe('activating items', () => {
        it('should activate on space keydown', () => {
          tabToBody();

          spaceDown();

          expect(grid.activeItem).to.equal('foo');
        });

        it('should activate item another on space keydown', () => {
          focusItem(0);
          clickItem(0);

          down();
          spaceDown();

          expect(grid.activeItem).to.equal('bar');
        });

        it('should deactive item on space keydown', () => {
          focusItem(0);
          clickItem(0); // activates first item on click

          spaceDown();

          expect(grid.activeItem).to.be.null;
        });

        it('should be null by default', () => {
          expect(grid.activeItem).to.be.null;
        });

        it('should activate on click', () => {
          clickItem(0);

          expect(grid.activeItem).to.equal('foo');
        });

        it('should activate another on click', () => {
          clickItem(0);

          clickItem(1);

          expect(grid.activeItem).to.equal('bar');
        });

        it('should deactive on click', () => {
          clickItem(0);

          clickItem(0);

          expect(grid.activeItem).to.be.null;
        });

        it('should activate when clicking on non-focusable child', () => {
          const span = getCellContent(getRowCell(0, 2)).children[0];
          // Normally non-focusables like span should not focus, but in MSIE they can
          span.focus();
          span.click();

          expect(grid.activeItem).to.equal('foo');
        });

        it('should not activate when clicking on a native input', () => {
          const input = focusFirstBodyInput(0);
          input.click();

          expect(grid.activeItem).to.be.null;
        });

        it('should not activate on space keydown click on a native input', () => {
          const input = focusFirstBodyInput(0);
          escape(input);
          spaceDown();

          expect(grid.activeItem).to.be.null;
        });

        it('should not deactivate on space keydown in non-body row', () => {
          clickItem(0);

          tabToHeader();
          spaceDown();

          expect(grid.activeItem).to.equal('foo');
        });

        it('should not deactivate on click in non-body row', () => {
          clickItem(0);

          focusFirstHeaderCell();

          expect(grid.activeItem).to.equal('foo');
        });

        it('should toggle when equaling item is clicked', () => {
          grid.itemIdPath = 'name';
          grid.items = [{name: 'foo'}, {name: 'bar'}];
          grid.activeItem = {name: 'foo'};
          clickItem(0);

          expect(grid.activeItem).to.be.null;
        });

        describe('space click shortcut', () => {
          beforeEach(() => {
            header.children[0].children[1].focus();
            right();
          });

          it('should click first cell child on space keyup', () => {
            const firstChild = getCellContent(header.children[0].children[2]).children[0];
            const clickStub = sinon.stub(firstChild, 'click');

            spaceDown();
            expect(clickStub.called).to.be.false;

            spaceUp();
            expect(clickStub.called).to.be.true;
          });

          it('should not click other cell children on space keyup', () => {
            const secondChild = getCellContent(header.children[0].children[2]).children[1];
            const clickStub = sinon.stub(secondChild, 'click');

            spaceDown();
            spaceUp();

            expect(clickStub.called).to.be.false;
          });

          it('should prevent default keydown action when clicking on space', () => {
            const event = MockInteractions.keyboardEventFor('keydown', 32, [], ' ');

            grid.dispatchEvent(event);

            expect(event.defaultPrevented).to.be.true;
          });

          it('should not activate if synthetic click has default prevented', () => {
            const firstBodyRowFirstChild = getCellContent(getRows(body)[0].children[2]).children[0];

            const spy = sinon.spy();
            listenOnce(firstBodyRowFirstChild, 'click', e => {
              spy();
              e.preventDefault();
            });

            // Navigate to body, row 1, column 3
            tabToBody();
            right();
            right();

            spaceDown();
            spaceUp();

            expect(spy.called).to.be.true;
            expect(grid.activeItem).to.be.null;
          });
        });
      });

      describe('keyboard focus', () => {
        it('should have focused first cell in header by default', () => {
          expect(grid.shadowRoot.activeElement).to.be.null;

          tabToHeader();

          expect(grid.shadowRoot.activeElement).to.equal(header.children[0].children[0]);
        });

        it('should have focused first cell in body by default', () => {
          expect(grid.shadowRoot.activeElement).to.be.null;

          tabToBody();

          expect(grid.shadowRoot.activeElement).to.equal(body.children[0].children[0]);
        });

        it('should have focused first cell in footer by default', () => {
          expect(grid.shadowRoot.activeElement).to.be.null;

          shiftTabToFooter();

          expect(grid.shadowRoot.activeElement).to.equal(footer.children[0].children[0]);
        });

        it('should focus on click', () => {
          focusItem(1);
          clickItem(1);

          const cell = getRowFirstCell(1);
          const row = cell.parentElement;
          const container = row.parentElement;

          expect(cell.hasAttribute('focused')).to.equal.true;
          expect(row.hasAttribute('focused')).to.equal.true;
          expect(container.hasAttribute('focused')).to.equal.true;
        });

        it('should not throw an error when switching focus between body and header', () => {
          expect(() => {
            focusItem(1);
            clickItem(1);
            getRowCells(getRows(grid.$.header)[0])[0].focus();
            getRowCells(getRows(grid.$.header)[0])[0].click();
            focusItem(0);
            clickItem(0);
          }).to.not.throw(Error);
        });
      });

      describe('interaction mode', () => {
        let input;

        beforeEach(() => {
          focusItem(0);
          clickItem(0);

          const cell = getRowCell(0, 1);
          input = getCellContent(cell).children[0];
        });

        it('should enter interaction mode with enter', () => {
          right();

          enter();

          expect(grid.hasAttribute('interacting')).to.be.true;
        });

        it('should exit interaction mode when blurred', () => {
          grid.setAttribute('interacting', '');

          focusable.focus();

          expect(grid.hasAttribute('interacting')).to.be.false;
        });

        it('should exit interaction mode when tabbed into', () => {
          grid.setAttribute('interacting', '');

          tabToHeader();

          expect(grid.hasAttribute('interacting')).to.be.false;
        });

        it('should exit interaction mode when shift-tabbed into', () => {
          grid.setAttribute('interacting', '');

          shiftTabToFooter();

          expect(grid.hasAttribute('interacting')).to.be.false;
        });

        it('should focus the first element when entering interaction mode with enter', () => {
          const cell = getRowCell(0, 1);
          const input = getCellContent(cell).children[0];
          const spy = sinon.spy(input, 'focus');

          right(); // focus the cell with input.

          enter();

          expect(spy.callCount).to.equal(1);
          spy.restore();
        });

        it('should exit interaction mode from focused single-line input with enter', () => {
          const cell = getRowCell(0, 1);
          const input = getCellContent(cell).children[0];
          input.type = 'text';

          right(); // focus the cell with input.
          enter();

          enter(input);

          expect(grid.hasAttribute('interacting')).to.be.false;
        });

        it('should not exit interaction mode from focused non-single-line input with enter', () => {
          const cell = getRowCell(0, 1);
          const input = getCellContent(cell).children[0];
          input.type = 'button';

          right(); // focus the cell with input.
          enter();

          enter(input);

          expect(grid.hasAttribute('interacting')).to.be.true;
        });

        it('should focus the first element when entering interaction mode with f2', () => {
          const cell = getRowCell(0, 1);
          const input = getCellContent(cell).children[0];
          const spy = sinon.spy(input, 'focus');

          right(); // focus the cell with input.

          f2();
          expect(spy.callCount).to.equal(1);
          spy.restore();
        });

        it('should focus the next input element when tabbing in interaction mode', () => {
          right(); // focus the cell with input.
          enter();

          tab(getCellContent(getRowCell(0, 1)).children[0]); // tab in the input

          // expecting focusable item cell to remain in place, instead actual focus moves.
          expect(grid._itemsFocusable).to.equal(getRowCell(0, 1));
        });

        it('should focus the element with `focus-target` when entering interaction mode', () => {
          const cell = getRowCell(0, 1);
          const input = getCellContent(cell).children[0];
          const spy = sinon.spy(input, 'focus');
          const div = document.createElement('div');
          input.parentElement.insertBefore(div, input);
          input.setAttribute('focus-target', '');

          right(); // focus the cell with input.

          enter();

          expect(spy.callCount).to.equal(1);

          input.removeAttribute('focus-target');
          input.parentElement.removeChild(div);
          spy.restore();
        });

        it('should not navigate with arrow up when in interaction mode', () => {
          const input = focusFirstBodyInput(1);

          up(input);
          escape(input);

          expect(getFocusedRowIndex()).to.equal(1);
          expect(getFocusedCellIndex()).to.equal(1);
        });

        it('should not navigate with arrow down when in interaction mode', () => {
          const input = focusFirstBodyInput(0);

          down(input);
          escape(input);

          expect(getFocusedRowIndex()).to.equal(0);
          expect(getFocusedCellIndex()).to.equal(1);
        });

        it('should not navigate with arrow left when in interaction mode', () => {
          right();
          const input = focusFirstBodyInput(0);

          left(input);
          escape(input);

          expect(getFocusedRowIndex()).to.equal(0);
          expect(getFocusedCellIndex()).to.equal(1);
        });

        it('should not navigate with arrow right when in interaction mode', () => {
          const input = focusFirstBodyInput(0);

          right(input);
          escape(input);

          expect(getFocusedRowIndex()).to.equal(0);
          expect(getFocusedCellIndex()).to.equal(1);
        });

        it('should not navigate with home when in interaction mode', () => {
          right();
          const input = focusFirstBodyInput(0);

          home(input);
          escape(input);

          expect(getFocusedRowIndex()).to.equal(0);
          expect(getFocusedCellIndex()).to.equal(1);
        });

        it('should not navigate with ctrl+home when in interaction mode', () => {
          right();
          const input = focusFirstBodyInput(0);

          ctrlHome(input);
          escape(input);

          expect(getFocusedRowIndex()).to.equal(0);
          expect(getFocusedCellIndex()).to.equal(1);
        });

        it('should not navigate with end when in interaction mode', () => {
          const input = focusFirstBodyInput(0);

          end(input);
          escape(input);

          expect(getFocusedRowIndex()).to.equal(0);
          expect(getFocusedCellIndex()).to.equal(1);
        });

        it('should not navigate with ctrl+end when in interaction mode', () => {
          const input = focusFirstBodyInput(0);

          ctrlEnd(input);
          escape(input);

          expect(getFocusedRowIndex()).to.equal(0);
          expect(getFocusedCellIndex()).to.equal(1);
        });

        it('should not navigate with page down when in interaction mode', () => {
          const input = focusFirstBodyInput(0);

          pageDown(input);
          escape(input);

          expect(getFocusedRowIndex()).to.equal(0);
          expect(getFocusedCellIndex()).to.equal(1);
        });

        it('should not navigate with page up when in interaction mode', () => {
          const input = focusFirstBodyInput(1);

          pageUp(input);
          escape(input);

          expect(getFocusedRowIndex()).to.equal(1);
          expect(getFocusedCellIndex()).to.equal(1);
        });

        it('should not activate on space keydown when in interaction mode', () => {
          grid.activeItem = null;
          const input = focusFirstBodyInput(0);

          spaceDown(input);

          expect(grid.activeItem).to.be.null;
        });

        it('should enter interaction mode with F2', () => {
          right();

          f2();

          expect(grid.hasAttribute('interacting')).to.be.true;
        });

        it('should exit interaction mode with F2', () => {
          const input = getCellContent(getRowCell(0, 1)).children[0];

          right();
          f2();

          f2(input);

          expect(grid.hasAttribute('interacting')).to.be.false;
        });

        it('should remove focus from cell when exiting interaction mode with F2', () => {
          const input = getCellContent(getRowCell(0, 1)).children[0];

          right();
          enter();

          f2(input);

          expect(document.activeElement).to.not.equal(input);
        });

        it('should exit interaction mode with escape', () => {
          grid.setAttribute('interacting', '');

          escape();

          expect(grid.hasAttribute('interacting')).to.be.false;
        });

        it('should remove focus from cell with escape', () => {
          const input = focusFirstBodyInput(0);

          escape(input); // revert to navigation first

          escape(); // unfortunately this does not trigger native blur
          focusable.focus(); // simulate native blur on escape

          expect(grid.hasAttribute('navigating')).to.be.false;
        });

        it('should revert to navigation from interaction mode with escape', () => {
          const input = focusFirstBodyInput(0);

          escape(input);

          expect(grid.hasAttribute('navigating')).to.be.true;
        });

        it('should revert to navigation from interaction mode with F2', () => {
          const input = focusFirstBodyInput(0);

          f2(input);

          expect(grid.hasAttribute('interacting')).to.be.false;
          expect(grid.hasAttribute('navigating')).to.be.true;
        });

        it('should cancel navigation mode with escape', () => {
          grid.setAttribute('navigating', '');
          grid.removeAttribute('interacting');

          escape();
          focusable.focus(); // simulate native blur on escape

          expect(grid.hasAttribute('navigating')).to.be.false;
        });

        it('should enter interaction mode when cell contents are focused', () => {
          focusFirstBodyInput(0);

          expect(grid.hasAttribute('interacting')).to.be.true;
        });
      });

      describe('focus events on cell content', () => {
        let cell;

        beforeEach(() => cell = getRowCell(0, 0));

        it('should dispatch cell-focusin after cell focus', () => {
          const spy = sinon.spy();
          cell._content.addEventListener('cell-focusin', spy);

          tabToBody();

          expect(spy.callCount).to.equal(1);
        });

        it('should dispatch cell-focusout after cell blur', () => {
          tabToBody();

          const spy = sinon.spy();
          cell._content.addEventListener('cell-focusout', spy);

          right();

          expect(spy.callCount).to.equal(1);
        });

        it('should dispatch cell-focusin on keyboard navigation', () => {
          tabToBody();
          right();

          const spy = sinon.spy();
          cell._content.addEventListener('cell-focusin', spy);

          left();

          expect(spy.callCount).to.equal(1);
        });

        it('should dispatch cell-focusout on keyboard navigation', () => {
          tabToBody();

          const spy = sinon.spy();
          cell._content.addEventListener('cell-focusout', spy);

          right();

          expect(spy.callCount).to.equal(1);
        });
      });
    });

    !ios && describe('keyboard navigation on column groups', () => {
      beforeEach(done => {
        grid = fixture('groups');
        grid.items = ['foo', 'bar'];
        flushGrid(grid);
        animationFrameFlush(done);
      });

      it('should focus first header row first', () => {
        tabToHeader();

        expect(grid.$.header.contains(grid.shadowRoot.activeElement)).to.be.true;
        expect(getFocusedRowIndex()).to.equal(0);
      });

      it('should focus header cell below with arrow down', () => {
        tabToHeader();

        down();

        expect(grid.$.header.contains(grid.shadowRoot.activeElement)).to.be.true;
        expect(getFocusedRowIndex()).to.equal(1);
      });

      it('should focus header cell above with arrow up', () => {
        tabToHeader();

        down();
        up();

        expect(getFocusedRowIndex()).to.equal(0);
      });

      it('should focus first footer cell first', () => {
        shiftTabToFooter();

        expect(grid.$.footer.contains(grid.shadowRoot.activeElement)).to.be.true;
        expect(getFocusedRowIndex()).to.equal(0);
      });

      it('should focus footer cell below with arrow down', () => {
        shiftTabToFooter();

        down();

        expect(grid.$.footer.contains(grid.shadowRoot.activeElement)).to.be.true;
        // Second how is hidden because of missing templates.
        // Should skip to the third one, index 2.
        expect(getFocusedRowIndex()).to.equal(2);
      });

      it('should focus footer cell above with arrow up', () => {
        shiftTabToFooter();

        down();
        up();

        expect(getFocusedRowIndex()).to.equal(0);
      });
    });
  </script>

</body>

</html>
